#include "znx_http_parse.h"
#include "znx_http_request.h"


znx_str_t
znx_http_uri_ext(znx_str_t *uri)
{
    size_t              i;
    u_char              ch;
    znx_str_t           uri_ext;

    uri_ext.data = NULL;
    uri_ext.len = 0;

    if (uri->len == 0) {
        return uri_ext;
    }

    for (i = 0; i < uri->len; i++) {
        ch = *(uri->data + i);
        if (ch == '/') {
            uri_ext.data = NULL;
            continue;
        }

        if (ch == '.') {
            uri_ext.data = uri->data + i;
            continue;
        }
    }

    if (uri_ext.data == NULL) {
        return uri_ext;
    }

    uri_ext.len = (size_t)(uri->data + uri->len - uri_ext.data);
    return uri_ext;
}


znx_result_t
znx_http_parse_header_line(znx_str_t *line, znx_http_header_t *header)
{
    u_char      *p;
    u_char      *end;
    u_char      ch, c;
    znx_str_t   header_name = ZNX_NULL_STRING;
    znx_str_t   header_value = ZNX_NULL_STRING;

    p = line->data;
    end = p + line->len;

    if (end - p < 2 || *(end - 1) != LF || *(end - 2) != CR) {
        return ZNX_HTTP_INVALID_HEADER_LINE;
    }

    header_name.data = p;
    for (; p != end; p++) {
        ch = *p;

        if (ch >= '0' && ch <= '9') {
            continue;
        }

        c = ch | 0x20;
        if (c >= 'a' && c <= 'z') {
            continue;
        }

        if (ch == '-' || ch == '_') {
            continue;
        }

        if (ch == ':') {
            header_name.len = (size_t)(p - header_name.data);
            break;
        }

        // other char, eg: ' ', '.', '\r', '\n' ...
        return ZNX_HTTP_INVALID_HEADER_LINE;
    }

    if (header_name.len == 0) {
        return ZNX_HTTP_INVALID_HEADER_LINE;
    }

    // skip char ':'
    p++;

    // parse space before header value
    for (; p != end; p++) {
        ch = *p;
        if (ch != ' ') {
            break;
        }
    }

    // parse header value
    header_value.data = p;
    for (; p != end; p++) {
        ch = *p;
        if (ch == LF) {
            // value may contain '\n' characters.
            if (p - header_value.data == 0 || *(p - 1) != CR) {
                continue;
            }

            header_value.len = (size_t)(p - header_value.data - 1);
            break;
        }
    }

    if (header_value.len == 0) {
        return ZNX_HTTP_INVALID_HEADER_LINE;
    }

    znx_http_header_name_init(&header->name, header_name.data, header_name.len);
    header->val = header_value;

    return ZNX_SUCCESS;
}


static znx_result_t
znx_http_parse_request_uri(znx_http_request_t *request, u_char **pos, u_char *end)
{
    u_char       ch;

    if (*pos == end || **pos != '/' || *(end - 1) != LF) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    // parse request_uri
    request->request_uri.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;
        if (ch == CR || ch == LF || ch == ' ') {
            request->request_uri.len = (size_t)(*pos - request->request_uri.data);
            break;
        }
    }

    if (request->request_uri.len == 0) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    // parse uri
    u_char      *rp = request->request_uri.data;
    u_char      *rend = rp + request->request_uri.len;

    request->uri.data = rp;
    for (; rp != rend; rp++) {
        ch = *rp;

        if (ch == '?') {
            request->uri.len = (size_t)(rp - request->uri.data);
            break;
        }

        if (ch == '#') {
            request->uri.len = (size_t)(rp - request->uri.data);
            goto FRAGMENT;
        }
    }

    if (request->uri.len == 0) {
        request->uri.len = (size_t)(rp - request->uri.data);
        return ZNX_SUCCESS;        
    }

    // parse args

    // skip char '?'
    rp++;

    request->args.data = rp;
    for (; rp != rend; rp++) {
        ch = *rp;
        if (ch == '#') {
            request->args.len = (size_t)(rp - request->args.data);
            break;
        }
    }

    if (request->args.len == 0) {
        request->args.len = (size_t)(rp - request->args.data);
        return ZNX_SUCCESS;
    }

FRAGMENT:

    // parse fragment

    // skip char '#'
    rp++;

    request->fragment.data = rp;
    request->fragment.len = (size_t)(rend - request->fragment.data);

    return ZNX_SUCCESS;
}


static znx_result_t
znx_http_parse_full_request_uri(znx_http_request_t *request, u_char **pos, u_char *end)
{
    u_char          ch;
    znx_str_t       full = ZNX_NULL_STRING;

    if (*pos == end || **pos != 'h' || *(end - 1) != LF) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    full.data = *pos;
    for (; *pos != end; (*pos)++) {
        ch = **pos;
        if (ch == CR || ch == LF || ch == ' ') {
            full.len = (size_t)(*pos - full.data);
            break;
        }
    }

    if (full.len == 0) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    // parse full uri
    u_char  *rp = full.data;
    u_char  *rend = rp + full.len;

    request->scheme.data = rp;
    for (; rp != rend; rp++) {
        ch = *rp;
        if (ch == ':') {
            request->scheme.len = (size_t)(rp - request->scheme.data);
            break;
        }
    }

    if (request->scheme.len == 0) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    // parse "//" before host.

    // skip char ':'
    rp++;
    if (rend - rp < 2 || rp[0] != '/' || rp[1] != '/') {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    rp += 2;

    // parse host

    if (rend - rp == 0) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

    if (*rp == '[') {
        rp++;
        request->host.data = rp;
        for (; rp != rend; rp++) {
            if (*rp == ']') {
                request->host.len = (size_t)(rp - request->host.data);
                break;
            }
        }

        if (request->host.len == 0) {
            return ZNX_HTTP_INVALID_REQUEST_URI;
        }

        // skip char ']'
        rp++;

        if (*rp == '/') {
            goto URI;
        }

        if (*rp != ':') {
            return ZNX_HTTP_INVALID_REQUEST_URI;
        }

        // to parse port
    } else {
        request->host.data = rp;
        for (; rp != rend; rp++) {
            if (*rp == '/') {
                request->host.len = (size_t)(rp - request->host.data);
                goto URI;
            }

            if (*rp == ':') {
                request->host.len = (size_t)(rp - request->host.data);
                break;
            }
        }

        if (request->host.len == 0) {
            return ZNX_HTTP_INVALID_REQUEST_URI;
        }

        // to parse port
    }

    // parse port

    // skip char ':'
    rp++;
    request->port.data = rp;
    for (; rp != rend; rp++) {
        if (*rp == '/') {
            request->port.len = (size_t)(rp - request->port.data);
            break;
        }
    }

    if (request->port.len == 0) {
        return ZNX_HTTP_INVALID_REQUEST_URI;
    }

URI:

    rend = end;
    return znx_http_parse_request_uri(request, &rp, rend);
}


// support the following formats.
// HTTP VERSION == 0.9 request line format: [METHOD][ ][URI?ARGS#REF][ ][\r\n]
// [METHOD][ ][CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT][URI?ARGS#REF][ ][\r\n]
// HTTP VERSION >= 1.0 request line format: [METHOD][ ][URI?ARGS#REF][ ][HTTP/VERSION][\r\n]
// [METHOD][ ][CONNECT_SCHEME://CONNECT_HOST:CONNECT_PORT][URI?ARGS#REF][ ][HTTP/VERSION][\r\n]
znx_result_t
znx_http_parse_request_line(znx_http_request_t *request)
{
    u_char          *p;
    u_char          *end;
    znx_result_t    res = ZNX_HTTP_INVALID_REQUEST_LINE;

    p = request->readline_sm.line.data;
    end = p + request->readline_sm.line.len;

    if (p == end || *(end - 1) != LF) {
        return ZNX_HTTP_INVALID_REQUEST_LINE;
    }

    // parse request method.
    request->method.data = p;
    for (; p != end; p++) {
        if (*p == ' ') {
            request->method.len = (size_t)(p - request->method.data);
            break;
        }

        if (*p == CR || *p == LF) {
            return ZNX_HTTP_INVALID_REQUEST_LINE;
        }
    }

    if (request->method.len == 0) {
        return ZNX_HTTP_INVALID_REQUEST_LINE;
    }

    // parse space before request uri
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

#if 0
    if (request->method.len == ZNX_HTTP_METHOD_CONNECT_LEN &&
        znx_strncmp(request->method.data,
            ZNX_HTTP_METHOD_CONNECT, ZNX_HTTP_METHOD_CONNECT_LEN) == 0)
    {
        // TODO: The CONNECT method is temporarily treated as an ordinary request.
        res = znx_http_parse_connect_request_line(request, &p, end);
    } else if (*p == '/') {
        res = znx_http_parse_request_uri(request, &p, end);
    } else if (*p == 'h') {
        res = znx_http_parse_full_request_uri(request, &p, end);
    } else {
        return ZNX_HTTP_INVALID_REQUEST_LINE;
    }
#else

    if (*p == '/') {
        res = znx_http_parse_request_uri(request, &p, end);
    } else if (*p == 'h') {
        res = znx_http_parse_full_request_uri(request, &p, end);
    } else {
        return ZNX_HTTP_INVALID_REQUEST_LINE;
    }

#endif

    if (res != ZNX_SUCCESS) {
        return res;
    }

    // parse space before http version
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    // parse http version
    znx_str_t   http_version_str;
    http_version_str.data = p;
    http_version_str.len = 0;
    for (; p != end; p++) {
        if (*p == CR || *p == LF || *p == ' ') {
            http_version_str.len = (size_t)(p - http_version_str.data);
            break;
        }
    }

    if (http_version_str.len == 0)
    {
        // request->http_version = ZNX_HTTP_VERSION_9;
        return ZNX_HTTP_NO_SUPPORT_VERSION_9;
    } else if (http_version_str.len == ZNX_HTTP_VERSION_10_STR_LEN &&
        znx_strncmp(http_version_str.data,
            ZNX_HTTP_VERSION_10_STR, ZNX_HTTP_VERSION_10_STR_LEN) == 0)
    {
        request->http_version = ZNX_HTTP_VERSION_10;
    } else if (http_version_str.len == ZNX_HTTP_VERSION_11_STR_LEN &&
        znx_strncmp(http_version_str.data,
            ZNX_HTTP_VERSION_11_STR, ZNX_HTTP_VERSION_11_STR_LEN) == 0)
    {
        request->http_version = ZNX_HTTP_VERSION_11;
    } else {
        return ZNX_HTTP_INVALID_HEADER_LINE;
    }

    return ZNX_SUCCESS;
}


znx_result_t
znx_http_parse_trailer_header(znx_pool_t *pool,
    znx_http_trailers_t *trailers, znx_str_t *value)
{
    znx_str_t                   tmp = ZNX_NULL_STRING;
    size_t                      i = 0;
    u_char                      ch;

    for (i = 0; i < value->len; i++) {
        ch = value->data[i];

        if (ch == ' ') {
            continue;
        }

        if (tmp.data == NULL) {
            tmp.data = value->data + i;
        }

        if (ch == ',') {
            tmp.len = (size_t)(value->data + i - tmp.data);
            if (tmp.len > 0) {
                trailers->total_trailers++;
            }

            ZNX_STR_SET_NULL(&tmp);
            continue;
        }
    }

    if (tmp.data && tmp.len == 0) {
        tmp.len = (size_t)(value->data + value->len - tmp.data);
    }

    if (tmp.len > 0) {
        trailers->total_trailers++;
    }

    return ZNX_SUCCESS;
}


znx_result_t
znx_http_parse_response_line(znx_http_request_t *request)
{
    u_char                  *p;
    u_char                  *end;
    znx_http_response_t     *response;
    znx_str_t               http_version_str = ZNX_NULL_STRING;
    znx_str_t               status_str = ZNX_NULL_STRING;

    response = request->response;

    p = request->readline_sm.line.data;
    end = p + request->readline_sm.line.len;

    if (p == end || *(end - 1) != LF) {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    // parse http version
    http_version_str.data = p;
    for (; p != end; p++) {
        if (*p == ' ') {
            http_version_str.len = (size_t)(p - http_version_str.data);
            break;
        }

        if (*p == CR || *p == LF) {
            return ZNX_HTTP_INVALID_RESPONSE_LINE;
        }
    }

    if (http_version_str.len == 0) {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    int http_version = 0;
    if (http_version_str.len == ZNX_HTTP_VERSION_10_STR_LEN &&
        znx_strncmp(http_version_str.data,
            ZNX_HTTP_VERSION_10_STR, ZNX_HTTP_VERSION_10_STR_LEN) == 0)
    {
        http_version = ZNX_HTTP_VERSION_10;
    } else if (http_version_str.len == ZNX_HTTP_VERSION_11_STR_LEN &&
        znx_strncmp(http_version_str.data,
            ZNX_HTTP_VERSION_11_STR, ZNX_HTTP_VERSION_11_STR_LEN) == 0)
    {
        http_version = ZNX_HTTP_VERSION_11;
    }

    if (http_version != request->http_version) {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    // parse space before response status code.
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    // parse http status
    status_str.data = p;
    for (; p != end; p++) {
        if (*p == ' ') {
            status_str.len = (size_t)(p - status_str.data);
            break;
        }

        if (*p == CR || *p == LF) {
            return ZNX_HTTP_INVALID_RESPONSE_LINE;
        }
    }

    if (status_str.len == 0) {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    int64_t     status = -1;
    if (!znx_str_to_int64(&status_str, &status) ||
        status < 100 ||
        status > 999)
    {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    response->status_code = (int)status;

    // parse space before response status reason.
    for (; p != end; p++) {
        if (*p != ' ') {
            break;
        }
    }

    response->status_reason.data = p;
    response->status_reason.len = (size_t)(end - p - 2);

    if (response->status_reason.len < 0) {
        return ZNX_HTTP_INVALID_RESPONSE_LINE;
    }

    return ZNX_FALSE;
}